package com.d.lib.ui.view.progress;

import com.makeramen.roundedimageview.RoundedImageView;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ohos.agp.components.*;
import ohos.agp.components.element.Element;
import ohos.agp.render.Arc;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.*;
import ohos.app.Context;
import ohos.media.image.PixelMap;

/**
 * CircleProgressBar
 * Created by D on 2018/3/30.
 */
public class CircleProgressBar extends StackLayout implements Component.ClickedListener,
    Component.DrawTask, Component.EstimateSizeListener {
  public static final int STATE_PROGRESS = 0;
  public static final int STATE_PENDING = 1;
  public static final int STATE_ERROR = 2;

  /**
   * [0]: Progress
   * [1]: Pendding
   * [2]: Error
   */
  private final int[] RES_IDS = new int[]{-1, -1, -1};
  private Point[] mPointsA;
  private Point[] mPointsB;
  private Point[] mPointsC;

  private int mWidth;
  private int mHeight;

  private Context mContext;
  private Paint mGPaint;
  private Path mGPath;

  private Paint mPaint;
  private Rect mRect;
  private RectFloat mRectF;
  private int mColorCircle;
  private int mColorArc;
  private int mDiameter;
  private int mSpace;
  private int[] mPaddingIcon;
  private float mStrokeWidth;

  private float mFactor;

  private int mState = STATE_PROGRESS;
  private boolean mIsFirst = true;
  private Request mRequest;
  private RoundedImageView mIvThumb;
  private RoundedImageView mIvAlpha;
  private RoundedImageView mIvState;
  private OnClickListener mListener;

  public CircleProgressBar(Context context) {
    this(context, null);
  }

  public CircleProgressBar(Context context, AttrSet attrs) {
    this(context, attrs, 0);
  }

  public CircleProgressBar(Context context, AttrSet attrs, int defStyleAttr) {
    super(context, attrs, String.valueOf(defStyleAttr));
    initAttrs(context, attrs);
    init(context);
  }

  private void initAttrs(Context context, @SuppressWarnings("unused") AttrSet attrs) {
    mColorCircle = Color.getIntColor("#33008577");
    mColorArc = Color.getIntColor("#008577");
    mSpace = AttrHelper.fp2px(2.5f, context);
    mPaddingIcon = new int[]{AttrHelper.fp2px(21.5f, context),
        AttrHelper.fp2px(20f, context)};
  }

  private void init(Context context) {
    this.mContext = context;
//        this.setWillNotDraw(false);
    mStrokeWidth = AttrHelper.fp2px(3.5f, context);
    LayoutConfig lp;
    mIvThumb = new RoundedImageView(context);
    mIvThumb.setScaleMode(Image.ScaleMode.CLIP_CENTER);
    lp = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
        ComponentContainer.LayoutConfig.MATCH_PARENT);
    lp.alignment = TextAlignment.CENTER;
    addComponent(mIvThumb, lp);

    mIvAlpha = new RoundedImageView(context);
    mIvAlpha.setScaleMode(Image.ScaleMode.CLIP_CENTER);
    mIvAlpha.setImageResource(Color.getIntColor("#00000000"));
    lp = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
        ComponentContainer.LayoutConfig.MATCH_PARENT);
    lp.alignment = TextAlignment.CENTER;
    addComponent(mIvAlpha, lp);

    mIvState = new RoundedImageView(context);
    mIvState.setScaleMode(Image.ScaleMode.CLIP_CENTER);
    lp = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_PARENT,
        ComponentContainer.LayoutConfig.MATCH_PARENT);
    lp.alignment = TextAlignment.CENTER;
    addComponent(mIvState, lp);
    setClickedListener(this::onClick);
    mRequest = new Request();
    mGPaint = new Paint();
    mGPaint.setColor(new Color(mColorCircle));
    mGPaint.setStyle(Paint.Style.FILL_STYLE);
    mGPath = new Path();

    mPaint = new Paint();
    mPaint.setColor(new Color(mColorCircle));
    mPaint.setStrokeWidth(mSpace);
    mPaint.setStyle(Paint.Style.STROKE_STYLE);

    mRect = new Rect();
    mRectF = new RectFloat();

    addDrawTask(this::onDraw);
    setEstimateSizeListener(this::onEstimateSize);
  }

  @Override
  public void onDraw(Component component, Canvas canvas) {
    draw(canvas, mState);
    mRect.set((int) ((mWidth - mDiameter) / 2f + mSpace / 2f),
        (int) ((mHeight - mDiameter) / 2f + mSpace / 2f),
        mWidth - (int) ((mWidth - mDiameter) / 2f + mSpace / 2f),
        mHeight - (int) ((mHeight - mDiameter) / 2f + mSpace / 2f));
    mRectF.modify(mRect);
    mPaint.setColor(new Color(mColorCircle));
    mPaint.setStrokeCap(Paint.StrokeCap.BUTT_CAP);
    canvas.drawArc(mRectF, new Arc(-90, 360, false), mPaint);
    mPaint.setColor(new Color(mColorArc));
    mPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
    canvas.drawArc(mRectF, new Arc(-90, mFactor, false), mPaint);
  }

  private void draw(Canvas canvas, @State int state) {
    switch (state) {
      case STATE_PROGRESS:
        draw(canvas, mColorArc, getPoints(0));
        draw(canvas, mColorArc, getPoints(1));
        break;
      case STATE_PENDING:
        draw(canvas, mColorArc, getPoints(2));
        break;
      case STATE_ERROR:
        mGPaint.setColor(new Color(mColorArc));
        canvas.translate(mWidth / 2, mHeight / 2);
        canvas.rotate(45);

        float factorB = 0.51f;
        mRect.set((int) (-mWidth / 2f * factorB), (int) (-mStrokeWidth / 2f),
            (int) (mWidth / 2f * factorB), (int) (mStrokeWidth / 2f));
        mRectF.modify(mRect);
        canvas.drawRect(mRectF, mGPaint);

        canvas.rotate(90);
        canvas.drawRect(mRectF, mGPaint);
        canvas.rotate(-135);
        canvas.translate(-mWidth / 2, -mHeight / 2);
        break;
    }
  }

  private Point[] getPoints(int type) {
    switch (type) {
      case 0:
        if (mPointsA == null) {
          float factorA = 0.56f;
          float startX = mWidth / 2f - mStrokeWidth * 1.63f;
          float startY = mHeight / 2f * factorA;
          mPointsA = new Point[]{new Point((int) (startX), (int) (startY)),
              new Point((int) (startX), (int) (mHeight - startY)),
              new Point((int) (startX + mStrokeWidth), (int) (mHeight - startY)),
              new Point((int) (startX + mStrokeWidth), (int) (startY))};
        }
        return mPointsA;
      case 1:
        if (mPointsB == null) {
          float factorA = 0.56f;
          float startX = mWidth / 2f + mStrokeWidth * 0.63f;
          float startY = mHeight / 2f * factorA;
          mPointsB = new Point[]{new Point((int) (startX), (int) (startY)),
              new Point((int) (startX), (int) (mHeight - startY)),
              new Point((int) (startX + mStrokeWidth), (int) (mHeight - startY)),
              new Point((int) (startX + mStrokeWidth), (int) (startY))};
        }
        return mPointsB;
      case 2:
      default:
        if (mPointsC == null) {
          mPointsC = new Point[]{new Point((int) (mWidth * (0.33f + 0.05f)),
              (int) (mHeight * 0.25f)),
              new Point((int) (mWidth * (0.66f + 0.05f)), (int) (mHeight * 0.5f)),
              new Point((int) (mWidth * (0.33f + 0.05f)), (int) (mHeight * 0.75f))};
        }
        return mPointsC;
    }
  }

  private void draw(Canvas canvas, int color, Point... points) {
    mGPaint.setColor(new Color(color));
    mGPath.reset();
    boolean first = true;
    for (Point p : points) {
      if (first) {
        first = false;
        mGPath.moveTo(p.getPointX(), p.getPointY());
        continue;
      }
      mGPath.lineTo(p.getPointX(), p.getPointY());
    }
    mGPath.close();
    canvas.drawPath(mGPath, mGPaint);
  }

  @Override
  public boolean onEstimateSize(int widthMeasureSpec, int heightMeasureSpec) {
    mWidth = EstimateSpec.getSize(widthMeasureSpec);
    mHeight = EstimateSpec.getSize(heightMeasureSpec);
    mDiameter = Math.min(mWidth, mHeight);
    mPaddingIcon[0] = (int) (mDiameter * 21.5f / 60f);
    mPaddingIcon[1] = (int) (mDiameter * 20f / 60f);
    mIvThumb.setPadding((int) ((mWidth - mDiameter) / 2f + mSpace),
        (int) ((mHeight - mDiameter) / 2f + mSpace),
        (int) ((mWidth - mDiameter) / 2f + mSpace), (int) ((mHeight - mDiameter) / 2f + mSpace));
    mIvAlpha.setPadding((int) ((mWidth - mDiameter) / 2f + mSpace),
        (int) ((mHeight - mDiameter) / 2f + mSpace),
        (int) ((mWidth - mDiameter) / 2f + mSpace), (int) ((mHeight - mDiameter) / 2f + mSpace));
    setEstimatedSize(EstimateSpec.getSizeWithMode(mWidth, EstimateSpec.PRECISE),
        EstimateSpec.getSizeWithMode(mHeight, EstimateSpec.PRECISE));
    if (mIsFirst && mDiameter > 0) {
      mIsFirst = false;
      setState(mState);
    }
    return true;
  }

  @Override
  public void onClick(Component component) {
    switch (mState) {
      case STATE_PROGRESS:
        if (mListener != null) {
          mListener.onPause();
        }
        break;
      case STATE_PENDING:
        if (mListener != null) {
          mListener.onResume();
        }
        break;
      case STATE_ERROR:
        if (mListener != null) {
          mListener.onRestart();
        }
        break;
    }
  }

  public Request setState(int state) {
    if (state < STATE_PROGRESS || state > STATE_ERROR) {
      return mRequest;
    }
    this.mState = state;
    switch (state) {
      case STATE_PROGRESS:
        mIvThumb.setVisibility(VISIBLE);
        mIvAlpha.setVisibility(HIDE);
        mIvState.setVisibility(HIDE);
        mIvState.setPadding((int) ((mWidth - mDiameter) / 2f), (int) ((mHeight - mDiameter) / 2f),
            (int) ((mWidth - mDiameter) / 2f), (int) ((mHeight - mDiameter) / 2f));
        if (RES_IDS[STATE_PROGRESS] != -1) {
          mIvState.setImageAndDecodeBounds(RES_IDS[STATE_PROGRESS]);
        } else {
          invalidate();
        }
        break;
      case STATE_PENDING:
        mIvThumb.setVisibility(VISIBLE);
        mIvAlpha.setVisibility(VISIBLE);
        mIvState.setVisibility(VISIBLE);
        mIvState.setPadding((int) ((mWidth - mDiameter) / 2f + mPaddingIcon[0]),
            (int) ((mHeight - mDiameter) / 2f + mPaddingIcon[1]),
            (int) ((mWidth - mDiameter) / 2f + mPaddingIcon[0]),
            (int) ((mHeight - mDiameter) / 2f + mPaddingIcon[1]));
        if (RES_IDS[STATE_PENDING] != -1) {
          mIvState.setImageAndDecodeBounds(RES_IDS[STATE_PENDING]);
        } else {
          invalidate();
        }
        break;
      case STATE_ERROR:
        mIvThumb.setVisibility(VISIBLE);
        mIvAlpha.setVisibility(HIDE);
        mIvState.setVisibility(VISIBLE);
        mIvState.setPadding((int) ((mWidth - mDiameter) / 2f), (int) ((mHeight - mDiameter) / 2f),
            (int) ((mWidth - mDiameter) / 2f), (int) ((mHeight - mDiameter) / 2f));
        if (RES_IDS[STATE_ERROR] != -1) {
          mIvState.setImageAndDecodeBounds(RES_IDS[STATE_ERROR]);
        } else {
          invalidate();
        }
        break;
    }
    return mRequest;
  }

  public void image(Image view, int id) {
    view.setImageAndDecodeBounds(id);
  }

  public void setOnClickListener(OnClickListener l) {
    this.mListener = l;
  }

  @Target({ElementType.PARAMETER})
  @Retention(RetentionPolicy.SOURCE)
  public @interface State {

  }

  public interface OnClickListener {
    void onRestart();

    void onResume();

    void onPause();
  }

  public class Request {
    public Request thumb(PixelMap bitmap) {
//            ShapeElement shapeElement = new ShapeElement();
      mIvThumb.setPixelMap(bitmap);
      return this;
    }

    public Request thumb(Element drawable) {
      mIvThumb.setImageElement(drawable);
      return this;
    }

    public Request progress(float progress) {
      mFactor = 360 * progress;
      invalidate();
      return this;
    }
  }
}
